home *** CD-ROM | disk | FTP | other *** search
/ World of Sound / World of Sound.iso / utils / miditools / midiwatcher / midiwatcher.c < prev    next >
C/C++ Source or Header  |  1993-08-14  |  15KB  |  570 lines

  1. /* MIDIwatcher v1.1, copyright ©1993 by Ed Mackey. */
  2. /* Source and binary are freely distributable in unmodified form. */
  3.  
  4. /*
  5.     Launch programs by playing tunes!
  6.  
  7.     Based on "hr" from midi.library, and requires midi.library.
  8.  
  9.     Shows proper method of waiting for, receiving, processing, and
  10.     disposing of MidiPackets.
  11. */
  12.  
  13. #include <stdio.h>
  14. #include <libraries/dos.h>
  15. #include <exec/lists.h>
  16. #include <clib/macros.h>
  17. #include <midi/midi.h>
  18. #include <functions.h>
  19. #include <rexx/rxslib.h>
  20.  
  21. struct library *RexxBase = NULL;
  22. struct MsgPort *myport = NULL, *RexxPort = NULL;
  23. struct RexxMsg *mymsg = NULL;
  24. struct RexxArg *myarg = NULL;
  25.  
  26. void *MidiBase;
  27.  
  28. struct MDest *dest;
  29. struct MRoute *route;
  30. struct MidiPacket *packet;  /* buffer this in case we get shut down before freeing the current message */
  31.  
  32. long record,tunes,debugON,OutThereMsgs,LineLen;
  33. FILE *myfile;
  34. char *sname = "MidiIn";
  35. char *fname = "S:midiwatch";
  36.  
  37. #define MAXTUNES 50
  38. #define MAXCHARS 200
  39.  
  40. struct NFAnode
  41. {
  42.     UWORD note;
  43.     WORD failnum;
  44.     struct NFAnode *next;
  45. } starters[MAXTUNES];
  46.  
  47. struct program
  48. {
  49.     char *port;
  50.     char *msg;
  51.     char *cmd;
  52. } progs[MAXTUNES], failprogs[MAXTUNES];
  53.  
  54. char tmp[MAXCHARS+2];
  55.  
  56. struct List InTunes;
  57. struct InTune
  58. {
  59.     struct Node node;
  60.     struct NFAnode *note;
  61.     WORD failnum;
  62.     WORD tunenum;    /* just for "-d" debug purposes */
  63.     long pos;    /* likewise */
  64. };
  65.  
  66. _abort(){}                  /* abort routine called when CTRL-C is hit (Aztec) */
  67.  
  68. /* The next routine is run if MidiWatcher tries to exit when there are
  69.  * ARexx messages that have been sent but not returned.
  70.  */
  71.  
  72. void WaitForOuts()
  73. {
  74.     ULONG flags = (1L << myport->mp_SigBit);
  75.  
  76.     printf("Waiting for ARexx messages to return...\n");
  77.     while (OutThereMsgs > 0) {
  78.     while (mymsg = (void *)GetMsg(myport))          /* assignment intended */
  79.     {
  80.         ClearRexxMsg(mymsg,1);
  81.         DeleteRexxMsg(mymsg);
  82.         OutThereMsgs--;
  83.     }
  84.     if (OutThereMsgs > 0) Wait(flags);
  85.     }
  86.     mymsg = NULL;
  87.     printf("Ah, all done.\n");
  88. }
  89.  
  90. void cleanup(int exc)
  91. {
  92.     if (mymsg) DeleteRexxMsg(mymsg);
  93.     if (packet) FreeMidiPacket (packet);
  94.     if (route) DeleteMRoute (route);
  95.     if (dest) DeleteMDest (dest);
  96.     if (MidiBase) CloseLibrary (MidiBase);
  97.  
  98.     if (OutThereMsgs > 0) WaitForOuts();
  99.  
  100.     if (myport) DeletePort(myport);
  101.     if (RexxPort)
  102.     {
  103.     Forbid();
  104.     while (mymsg = (void *)GetMsg(RexxPort))  /* Assignment intended */
  105.     {
  106.         mymsg -> rm_Result1 = mymsg -> rm_Result2 = 0;
  107.         ReplyMsg((void *)mymsg);
  108.     }
  109.     DeletePort(RexxPort);
  110.     Permit();
  111.     }
  112.     if (RexxBase) CloseLibrary (RexxBase);
  113.     if (myfile) fclose(myfile);
  114.  
  115.     exit(exc);
  116. }
  117.  
  118. void die(int exc, char *msg)
  119. {
  120.     if (msg) printf("%s\n",msg);
  121.     cleanup(exc);
  122. }
  123.  
  124. #if 0
  125. dump (s,len)                                    /* print len bytes in hex */
  126. UBYTE *s;
  127. int len;
  128. {
  129.     while (len--) printf ("%02x ",*s++);
  130. }
  131. #endif
  132.  
  133. /* The next routine takes action when a tune is recognized, or when there
  134.  * is a failure past the "point of no return".
  135.  */
  136.  
  137. void ExeProg(UWORD prognum, int FailYesNo)
  138. {
  139.     struct program *tp;
  140.     struct MsgPort *mp;
  141.  
  142.     if (FailYesNo)
  143.         tp = failprogs + prognum;
  144.     else
  145.         tp = progs + prognum;
  146.     if (debugON)
  147.     {
  148.         if (FailYesNo)
  149.             printf("Running failure program %d\n",prognum);
  150.         else
  151.             printf("Running program %d\n",prognum);
  152.         if (tp -> port) printf("ARexx port : %s\n",tp -> port);
  153.         if (tp -> msg)  printf("ARexx msg  : %s\n",tp -> msg);
  154.         if (tp -> cmd)  printf("CLI command: %s\n\n",tp -> cmd);
  155.     }
  156.     if ((tp -> port) && (tp -> msg))
  157.     {
  158.         if (!RexxBase)
  159.             printf("I need " RXSNAME " for ARexx messages!\n");
  160.         else
  161.         {
  162.             mymsg = CreateRexxMsg(myport,NULL,tp -> port);
  163.             if (!mymsg) die(10,"Out of mem!");
  164.             mymsg -> rm_Args[0] = (void *)(tp -> msg);
  165.             if (!FillRexxMsg(mymsg,1,0)) die(10,"Out of mem");
  166.             Forbid();
  167.             if (mp = FindPort(tp -> port))  /* assignment intended */
  168.             {
  169.                 PutMsg(mp,(void *)mymsg);
  170.                 Permit();
  171.                 OutThereMsgs++;
  172.             } else {
  173.                 Permit();
  174.                 ClearRexxMsg(mymsg,1);
  175.                 DeleteRexxMsg(mymsg);
  176.             }
  177.             mymsg = NULL;
  178.         }
  179.     }
  180.     if (tp -> cmd) Execute((tp -> cmd),0,0);
  181. }
  182.  
  183. /* The next routine handles incoming MIDI messages. */
  184.  
  185. int dopak (UBYTE *s, int len)  /* Message pointer, Message length */
  186. {
  187.     struct InTune *tp, *t2;
  188.     struct NFAnode *np, *n2;
  189.     int t;
  190.  
  191.     if (len < 2) return(0);    /* Too short?  Ignore it. */
  192.     if ((s[0] < 0x90) || (s[0] > 0x9f)) return(0);  /* Not a NoteDn?  Ignore it! */
  193.     if (record)    /* RECORD MODE */
  194.     {
  195.         if ((LineLen += 3) > 75)
  196.         {
  197.             fprintf(myfile,"%02x\n ",s[1]);
  198.             LineLen = 0;
  199.         } else {
  200.             fprintf(myfile,"%02x ",s[1]);
  201.         }
  202.     } else {    /* PLAYBACK MODE */
  203.         tp = (void *)InTunes.lh_Head;
  204.         while (t2 = (void *)(tp -> node.ln_Succ))   /* Assignment intended! */
  205.         {
  206.             ++(tp -> pos);
  207.             np = tp -> note;
  208.             n2 = NULL;
  209.             if (s[1] == (np -> note)) n2 = np -> next;
  210.             tp -> note = n2;
  211.             if (n2)
  212.             {
  213.                 if (debugON) printf("Tune %d:%d continues.  Matched note %02x.\n",
  214.                     tp -> tunenum, tp -> pos, s[1]);
  215.                 if ((n2 -> failnum) >= 0)
  216.                 {
  217.                     tp -> failnum = n2 -> failnum;
  218.                     if (debugON) printf("Set failure of %d:%d to %d.\n",
  219.                         tp -> tunenum, tp -> pos, tp -> failnum);
  220.                 }
  221.                 if ((n2 -> note) > 255)
  222.                 {
  223.                     if (debugON) printf("Tune %d completed!\n",
  224.                         tp -> tunenum);
  225.                     tp -> failnum = -1;
  226.                     ExeProg((n2 -> note) - 256, 0);
  227.                 }
  228.             } else {
  229.                 if (debugON && ((np -> note) < 0x100))
  230.                     printf("Tune %d:%d failed.  Expecting note %02x, got note %02x.\n",
  231.                         tp -> tunenum, tp -> pos, np -> note, s[1]);
  232.                 if (tp -> failnum >= 0) ExeProg(tp -> failnum, 1);
  233.                 Remove((void *)tp);
  234.                 free((void *)tp);
  235.             }
  236.             tp = t2;
  237.         }
  238.         np = starters;
  239.         for (t = 0; t < tunes; t++)
  240.         {
  241. /* This one just got too messy. */
  242. //            if (debugON) printf("Checking start note %04x, got note %04x\n",np -> note, s[1]);
  243.             if ((np -> note) == s[1])
  244.             {
  245.                 if (debugON) printf("Started tune %d\n",t);
  246.                 if (!(tp = (void *)malloc(sizeof(struct InTune)))) die(10,"Mem!!");
  247.                 tp -> tunenum = t;
  248.                 tp -> pos = 1;
  249.                 n2 = tp -> note = np -> next;
  250.                 tp -> failnum = n2 -> failnum;
  251.                 if ((n2 -> note) > 255)
  252.                 {
  253.                     ExeProg((n2 -> note) - 256, 0);
  254.                     free((void *)tp);
  255.                 } else {
  256.                     AddTail(&InTunes,(void *)tp);
  257.                 }
  258.             }
  259.             np++;
  260.         }
  261.     }
  262. }
  263.  
  264. dumppacket (packet)
  265. struct MidiPacket *packet;
  266. {
  267.     if (packet->Type == MMF_SYSEX) {                /* if it's a System Exclusive message... */
  268. //    dump (packet->MidiMsg,MIN(packet->Length,8));   /* only print the first 8 bytes */
  269. //    printf ("... (%d bytes)\n",packet->Length);
  270. ;                            /* or just ignore it ;-) */
  271.     }
  272.     else {                        /* otherwise */
  273.     dopak (packet->MidiMsg,packet->Length);          /* process the message */
  274.     }
  275. }
  276.  
  277. void Suspend()
  278. {
  279.     ULONG flags = SIGBREAKF_CTRL_C | (1L << myport->mp_SigBit) | (1L << RexxPort->mp_SigBit);
  280.     int KGS = 0;
  281.  
  282.     if (route) DeleteMRoute (route);
  283.     route = NULL;
  284.     if (dest) DeleteMDest (dest);
  285.     dest = NULL;
  286.  
  287.     while ((!KGS) && (!(Wait(flags) & SIGBREAKF_CTRL_C))) {
  288.         while (mymsg = (void *)GetMsg(myport))          /* assignment intended */
  289.         {
  290.             ClearRexxMsg(mymsg,1);
  291.             DeleteRexxMsg(mymsg);
  292.             OutThereMsgs--;
  293.         }
  294.         while (mymsg = (void *)GetMsg(RexxPort))  /* Assignment intended */
  295.         {
  296.             tmp[0] = 0;
  297.             if (strlen((void *)(mymsg -> rm_Args[0])) < MAXCHARS)
  298.                 strcpy(tmp,(void *)(mymsg -> rm_Args[0]));
  299.             mymsg -> rm_Result1 = mymsg -> rm_Result2 = 0;
  300.             ReplyMsg((void *)mymsg);
  301.             mymsg = NULL;
  302.             if (!strcmp(tmp,"QUIT")) die(0,NULL);
  303.             if (!strcmp(tmp,"RESUME")) KGS = 1;
  304.         }
  305.     }
  306.     if (!KGS) die(0,NULL);  /* CTRL-C check */
  307.  
  308.             /* create our dest node (private) */
  309.     if (!(dest = CreateMDest (NULL,NULL))) {
  310.         printf ("can't create Dest\n");
  311.         cleanup(10);
  312.     }
  313.             /* create our route to MidiIn or whatever the user specified */
  314.     if (!(route = MRouteDest (sname, dest, NULL))) {        /* use default route on our MDest (defaults to everything) */
  315.         printf ("can't create Route (can't find source \"%s\"?)\n",sname);
  316.         cleanup(10);
  317.     }
  318. }
  319.  
  320. process ()
  321. {
  322.     ULONG flags = SIGBREAKF_CTRL_C | (1L << dest->DestPort->mp_SigBit) | (1L << myport->mp_SigBit);
  323.  
  324.     if (RexxPort) flags |= (1L << RexxPort->mp_SigBit);
  325.     while (!(Wait(flags) & SIGBREAKF_CTRL_C)) {         /* wait until we get a message or CTRL-C is hit, quit on CTRL-C */
  326.     while (packet = GetMidiPacket (dest)) {         /* get a packet */
  327.         dumppacket (packet);                        /* print it */
  328.         FreeMidiPacket (packet);                    /* free it */
  329.     }
  330.     while (mymsg = (void *)GetMsg(myport))          /* assignment intended */
  331.     {
  332.         ClearRexxMsg(mymsg,1);
  333.         DeleteRexxMsg(mymsg);
  334.         OutThereMsgs--;
  335.     }
  336.     if (RexxPort) {
  337.         while (mymsg = (void *)GetMsg(RexxPort))  /* Assignment intended */
  338.         {
  339.             tmp[0] = 0;
  340.             if (strlen((void *)(mymsg -> rm_Args[0])) < MAXCHARS)
  341.                 strcpy(tmp,(void *)(mymsg -> rm_Args[0]));
  342.             mymsg -> rm_Result1 = mymsg -> rm_Result2 = 0;
  343.             ReplyMsg((void *)mymsg);
  344.             mymsg = NULL;
  345.             if (!strcmp(tmp,"QUIT")) return();
  346.             if (!strcmp(tmp,"SUSPEND"))
  347.             {
  348.                 Suspend();
  349.                 flags = SIGBREAKF_CTRL_C |
  350.                  (1L << dest->DestPort->mp_SigBit) |
  351.                  (1L << myport->mp_SigBit) |
  352.                  (1L << RexxPort->mp_SigBit);
  353.             }
  354.         }
  355.     }
  356.     }
  357. }
  358.  
  359. /* The next routine makes an NFA (Nondeterministic Finite-state Automaton) out
  360.  * of the user's tunes.  I learned about them in CSc 318 at Lehigh, with
  361.  * professor Ed Kay.  I always meant to do an NFA -> DFA conversion, so as to
  362.  * take less CPU time during the actual realtime processing of incoming notes.
  363.  * I learned the conversion in 318 also, but I never had the time or energy to
  364.  * implement it in the context of this silly program.
  365.  *
  366.  * Feel free to try it yourself.
  367.  * If you succeed, you _must_ send me a copy.
  368.  */
  369.  
  370. void MakeNFA()
  371. {
  372.     long mode,ch,hex;
  373.     char *tmp2, *tp;
  374.     struct NFAnode *n1, *n2;
  375.  
  376.     mode = -1L;
  377.     while ((ch = fgetc(myfile)) != EOF)
  378.     {
  379.         if ((!mode) && (((ch >= '0') && (ch <= '9')) || ((ch >= 'a') && (ch <= 'f'))))
  380.         {
  381.             tmp[0] = ch; tmp[1] = fgetc(myfile); tmp[2] = 0;
  382.             sscanf(tmp,"%lx",&hex);
  383.             n2 = (void *)malloc(sizeof(struct NFAnode));
  384.             if (!n2) die(10,"Out of mem");
  385.             n2 -> failnum = -1;
  386.             n1 -> note = hex;
  387.             n1 -> next = n2;
  388.             n2 -> note = tunes + 256;
  389.             n2 -> next = NULL;
  390.             n1 = n2;
  391.         } else if (mode > 0) {
  392.             if (ch == 10)
  393.             {
  394.                 *tp = 0;
  395.                 if (tmp[0])
  396.                 {
  397.                     tmp2 = (void *)malloc(strlen(tmp) + 1);
  398.                     if (!tmp2) die(10,"Out of mem");
  399.                     strcpy(tmp2,tmp);
  400.                     switch(mode)
  401.                     {
  402.                         case 1: progs[tunes].cmd = tmp2; break;
  403.                         case 2: progs[tunes].port = tmp2; break;
  404.                         case 3: progs[tunes].msg = tmp2; break;
  405.                         case 4: failprogs[tunes].cmd = tmp2; break;
  406.                         case 5: failprogs[tunes].port = tmp2; break;
  407.                         case 6: failprogs[tunes].msg = tmp2; break;
  408.                     }
  409.                 }
  410.                 mode = -1;
  411.             } else {
  412.                 *(tp++) = ch;
  413.                 if ((ULONG)tp >= (ULONG)(tmp + MAXCHARS)) tp--;
  414.             }
  415.         } else if (mode == -2) {
  416.             if (ch == 10) mode = -1;
  417.         } else switch(ch) {
  418.             case '$': mode = 0; tmp[0] = 0;
  419.                   if ((++tunes) >= MAXTUNES) die(10,"Too many tunes");
  420.                   progs[tunes].port = NULL;
  421.                   progs[tunes].msg = NULL;
  422.                   progs[tunes].cmd = NULL;
  423.                   failprogs[tunes].port = NULL;
  424.                   failprogs[tunes].msg = NULL;
  425.                   failprogs[tunes].cmd = NULL;
  426.                   n1 = &(starters[tunes]);
  427.                   n1 -> note = 0;
  428.                   n1 -> next = NULL;
  429.                   n1 -> failnum = -1;
  430.                   break;
  431.             case ':': if (!mode) n1 -> failnum = tunes; break;
  432.             case '>': mode = 1; tp = tmp; tmp[0] = 0; break;
  433.             case '@': mode = 2; tp = tmp; tmp[0] = 0; break;
  434.             case '*': mode = 3; tp = tmp; tmp[0] = 0; break;
  435.             case ']': mode = 4; tp = tmp; tmp[0] = 0; break;
  436.             case '+': mode = 5; tp = tmp; tmp[0] = 0; break;
  437.             case '!': mode = 6; tp = tmp; tmp[0] = 0; break;
  438.             case '#': mode = -2; break;
  439.         }
  440.     }
  441.     tunes++;
  442. }
  443.  
  444. main(argc,argv)
  445. char **argv;
  446. {
  447.     int t;
  448.  
  449.     record = OutThereMsgs = debugON = LineLen = 0;
  450.     myfile = NULL;
  451.     tunes = -1;
  452.  
  453.     if (argc > 1 && *argv[1] == '?') {
  454.     printf("MidiWatcher v1.1 by Ed Mackey.  Watch MIDI keyboard for tunes.\n");
  455.     printf("Usage: %s [-i source] [-f savefile] [-r] [-d]\n",argv[0]);
  456.     printf("  -i selects a MidiIn source, defaults to MidiIn\n");
  457.     printf("  -f selects a tune save file, defaults to S:midiwatch\n");
  458.     printf("  -r turns on RECORD mode and adds a tune to file -f\n");
  459.     printf("  -d turns on DEBUG mode (playback only) to see your tunes go.\n");
  460.     exit (1);
  461.     }
  462.  
  463.     if (!(MidiBase = OpenLibrary (MIDINAME,MIDIVERSION))) {
  464.     printf ("can't open midi.library\n");
  465.     cleanup(10);
  466.     }
  467.  
  468.     RexxBase = OpenLibrary(RXSNAME,0L);  /* RexxBase may be NULL after this! */
  469.  
  470.     myport = CreatePort(NULL,0);
  471.     if (!myport) die(10,"No mem for replyport");
  472.  
  473.     t = 1;
  474.     while (argc > t)
  475.     {
  476.     if (argv[t][0] == '-')
  477.     {
  478.         switch(argv[t][1])
  479.         {
  480.             case 'i': if (argv[t][2]) sname = &(argv[t][2]);
  481.                     else sname = argv[++t];
  482.                   break;
  483.             case 'f': if (argv[t][2]) fname = &(argv[t][2]);
  484.                     else fname = argv[++t];
  485.                   break;
  486.             case 'r': record = 1; break;
  487.             case 'd': debugON = 1; break;
  488.         }
  489.     }
  490.     t++;
  491.     }
  492.                 /* create our dest node (private) */
  493.     if (!(dest = CreateMDest (NULL,NULL))) {
  494.     printf ("can't create Dest\n");
  495.     cleanup(10);
  496.     }
  497.                 /* create our route to MidiIn or whatever the user specified */
  498.     if (!(route = MRouteDest (sname, dest, NULL))) {        /* use default route on our MDest (defaults to everything) */
  499.     printf ("can't create Route (can't find source \"%s\"?)\n",sname);
  500.     cleanup(10);
  501.     }
  502.  
  503.     if (record)
  504.     {
  505.     myfile = fopen(fname,"a");
  506.     printf("Play a tune and press CTRL-C\n");
  507.     } else {
  508.     myfile = fopen(fname,"rb");
  509.     }
  510.  
  511.     if (!myfile)
  512.     {
  513.     printf("Unable to open file %s\n",fname);
  514.     cleanup(10);
  515.     }
  516.  
  517.     NewList(&InTunes);
  518.  
  519.     if (record)
  520.     fprintf(myfile,"# Your comments go here (such as name of tune)...\n"
  521.         "# You may wish to insert a colon (\":\") at the point of no return...\n$");
  522.     else
  523.     {
  524.     MakeNFA();
  525.     if (debugON) printf("Loaded %d tunes\n",tunes);
  526.     if (RexxBase)
  527.     {        /* If MidiWatcher is already running, try to kill it. */
  528.         struct MsgPort *mp;
  529.  
  530.         mymsg = CreateRexxMsg(myport,NULL,"MIDIWATCHER");
  531.         if (!mymsg) die(10,"Out of mem!");
  532.         mymsg -> rm_Args[0] = (void *)("QUIT");
  533.         if (!FillRexxMsg(mymsg,1,0)) die(10,"Out of mem");
  534.         Forbid();
  535.         if (mp = FindPort("MIDIWATCHER"))  /* assignment intended */
  536.         {
  537.             PutMsg(mp,(void *)mymsg);
  538.             Permit();
  539.             OutThereMsgs++;
  540.         } else {
  541.             Permit();
  542.             ClearRexxMsg(mymsg,1);
  543.             DeleteRexxMsg(mymsg);
  544.         }
  545.         mymsg = NULL;
  546.     }
  547.     RexxPort = CreatePort("MIDIWATCHER",4L);
  548.     if (!RexxPort) die(10,"No mem for port");
  549.     }
  550.  
  551.     process();          /* process until shutdown */
  552.  
  553.     if (record)
  554.     {
  555.     printf("Saving and exiting...\n");
  556.     fprintf(myfile,"\n"
  557.         "#### Success program...\n"
  558.         "@PLAY\n"
  559.         "*id midi_event\n"
  560.         ">echo \"Tune recognized\"\n"
  561.         "#### Failure program...\n"
  562.         "+PLAY\n"
  563.         "!id midi_fail\n"
  564.         "]echo \"You passed the colon, and then blew it!!\"\n\n");
  565.     }
  566.     cleanup(0);
  567. }
  568.  
  569. /* End of midiwatcher.c */
  570.